﻿using System;
using System.IO;
using System.Linq;
using NHibernate.Exceptions;
using NUnit.Framework;
using SellAndBuy.Domain.Entities;
using SellAndBuy.Application.Query.Finders;
using SellAndBuy.Domain.Repositories;
using SellAndBuy.Infrastructure.Core.Logging;
using SellAndBuy.Infrastructure.Core.Logging.NullLogger;
using SellAndBuy.Infrastructure.Data.Core;
using SellAndBuy.Infrastructure.Data.Core.Factories;
using SellAndBuy.Infrastructure.Data.Query.Finders;
using SellAndBuy.Infrastructure.Data.Repositories;

namespace SellAndBuy.Infrastructure.Data.Tests
{
    [TestFixture]
    public class AdRepositoryTests
    {
        private NHibernateFactory _factory;
        private INhibernateUnitOfWork _unitOfWork;
        private ILogger _logger;

        private ICustomerRepository GetCustomerRepository()
        {
            return new CustomerRepository(_unitOfWork);
        }

        private ICustomerFinder GetCustomerFinder()
        {
            return new CustomerFinder(_unitOfWork);
        }

        private IAdRepository GetAdRepository()
        {
            return new AdRepository(_unitOfWork);
        }

        private IAdFinder GetAdFinder()
        {
            return new AdFinder(_unitOfWork);
        }

        private IItemRepository GetItemRepository()
        {
            return new ItemRepository(_unitOfWork);
        }

        private IItemFinder GetItemFinder()
        {
            return new ItemFinder(_unitOfWork);
        }

        private ICategoryRepository GetCategoryRepository()
        {
            return new CategoryRepository(_unitOfWork);
        }

        private ICategoryFinder GetCategoryFinder()
        {
            return new CategoryFinder(_unitOfWork);
        }

        private static Customer CreateNewCustomer()
        {
                return new Customer
                {
                    FirstName = "Mohamed",
                    LastName = "Abed",
                    Email = "moh.abed@gmail.com"
                };
        }

        private static ItemCategory CreateNewCategory(string name)
        {
            var category = new ItemCategory { Name = name };
            category.AddCondition("Bad");
            category.AddCondition("Good");
            category.AddCondition("Very Good");
            category.AddCondition("As New");
            return category;
        }

        private static Ad CreateNewAd(Customer customer, Item item, string description, decimal price, string condition, params string[] picFilePathes)
        {
            if(customer == null) 
                customer = CreateNewCustomer();

            var ad = new Ad(customer, item)
                         {
                             Description = description,
                             Price = price,
                             Condition = item.Category.GetCondition(condition)
                         };

            foreach (var picFilePath in picFilePathes)
            {
                var fileStream = File.OpenRead(picFilePath);
                var data = new byte[fileStream.Length];
                fileStream.Read(data, 0, data.Length);

                ad.AddPicture(data, Path.GetFileName(picFilePath), picFilePath);
            }
           
            return ad;
        }

        private ItemCategory CreateAndGetCategory(string name)
        {
            var category = CreateNewCategory(name);
            var categoryRepository = GetCategoryRepository();
            categoryRepository.Add(category);
            categoryRepository.UnitOfWork.Commit();

            return categoryRepository.FindBy(cat => cat.Name == name);
        }

        private Item CreateAndGetItem(string title, string description, ItemCategory category)
        {
            var item = new Item(category) { Name = title, Description = description };
            var itemRepository = GetItemRepository();
            itemRepository.Add(item);
            itemRepository.UnitOfWork.Commit();
            return itemRepository.FindBy(i => i.Name == title);
        }

        [SetUp]
        public void InitializeSchema()
        {
            log4net.Config.XmlConfigurator.Configure();
            _logger = new NullLogger();
            _factory = new NHibernateFactory();
            _unitOfWork = new UnitOfWork(_factory/*, _logger*/);

            _factory.CreateSchema();
        }

        [Test]
        public void Adding_Valid_Customer_To_Repository_And_Get_It_Back()
        {
            var customer = CreateNewCustomer();

            var customerRepository = GetCustomerRepository();
            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);

            Assert.NotNull(returnedCustomer);
            Assert.AreEqual(customer.FirstName, returnedCustomer.FirstName);
            Assert.AreEqual(customer.LastName, returnedCustomer.LastName);
            Assert.AreEqual(customer.Email, returnedCustomer.Email);
        }       

        [Test]
        public void Create_Ad_And_Get_It_Back()
        {
            var customer = CreateNewCustomer();

            var customerRepository = GetCustomerRepository();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var AdRepository = GetAdRepository();

            var category = CreateAndGetCategory("Video Games");
            var item = CreateAndGetItem("XBox 360", "XBox 360 Black", category);

            var Ad = CreateNewAd(customer, item, "XBox 360 Black Description", 1800, "Good", 
                                                   @"Resource\Xbox360.jpg");
            AdRepository.Add(Ad);

            AdRepository.UnitOfWork.Commit();

            var returnedAd = AdRepository.FindBy(ad => ad.Description == Ad.Description);

            Assert.NotNull(returnedAd);
            Assert.AreEqual(Ad.Item.Name, returnedAd.Item.Name);
            Assert.AreEqual(Ad.Description, returnedAd.Description);
            Assert.AreEqual(Ad.Price, returnedAd.Price);
            Assert.AreEqual(Ad.Customer.ID, returnedAd.Customer.ID);
            Assert.NotNull(Ad.MainPicture);
        }

        [Test]
        public void Create_Multiple_Ads_And_Get_Them_Back()
        {
            var customer = CreateNewCustomer();

            var customerRepository = GetCustomerRepository();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var AdRepository = GetAdRepository();

            var category = CreateAndGetCategory("Video Games");
            var item = CreateAndGetItem("XBox 360", "XBox 360 Black", category);

            var Ad = CreateNewAd(customer, item, "XBox 360 Black Description", 1800, "Good",
                                                   @"Resource\Xbox360.jpg");

            AdRepository.Add(Ad);

            Ad = CreateNewAd(customer, item, "Xbox 360 Halo 3rd Edition Console", 2400, "Very Good",
                                                   @"Resource\xbox360Halo3EditionConsole.jpg", @"Resource\Xbox360.jpg");
            AdRepository.Add(Ad);

            Ad = CreateNewAd(customer, item, "Xbox 360 Wireless Controller", 450, "As New", 
                                                   @"Resource\Xbox360WirelessController.jpg");
            AdRepository.Add(Ad);

            AdRepository.UnitOfWork.Commit();

            var returnedAds = GetAdFinder().FindAll();

            Assert.NotNull(returnedAds);
            var adsList = returnedAds.ToList();
            Assert.AreEqual(3, adsList.Count());
            Assert.AreEqual(Ad.Item.Name, adsList[2].Title);
            Assert.AreEqual(Ad.Description, adsList[2].Description);
            Assert.AreEqual(Ad.Price, adsList[2].Price);
            //Assert.AreEqual(Ad.Customer.ID, adsList[2].Customer.ID);
            //Assert.NotNull(adsList[2].MainPicture);
        }


        [Test]
        public void Adding_Null_Item_Throws_Exception()
        {
            var adRepository = GetAdRepository();

            Assert.Throws<ArgumentNullException>(() => adRepository.Add((Ad) null));
        }

        [Test]
        public void Adding_Duplicate_Item_Throws_Exception()
        {
            var customerRepository = GetCustomerRepository();
            
            customerRepository.Add(CreateNewCustomer());
            customerRepository.Add(CreateNewCustomer());

            Assert.Throws<GenericADOException>(() => customerRepository.UnitOfWork.Commit());
        }

        [Test]
        public void Rolling_Back_Transaction()
        {
            var customerRepository = GetCustomerRepository();

            var customer = CreateNewCustomer();
            customerRepository.Add(customer);

            var returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email); 
            Assert.NotNull(returnedCustomer);

            customerRepository.UnitOfWork.Rollback();

            returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.Null(returnedCustomer);
        }

        [Test]
        public void Recover_From_Exception()
        {
            var customerRepository = GetCustomerRepository();

            try
            {
                customerRepository.Add(CreateNewCustomer());
                customerRepository.Add(CreateNewCustomer());
                customerRepository.UnitOfWork.Commit();
            }
            catch (GenericADOException)
            { }

            customerRepository.Add(new Customer { Email = "new@mail.com", FirstName = "Sample", LastName = "Sample"});
            customerRepository.UnitOfWork.Commit();

            var validCustomer = GetCustomerFinder().FindByEmail("new@mail.com");
            Assert.NotNull(validCustomer);
            Assert.AreEqual("Sample", validCustomer.FirstName);
        }

        [Test]
        public void Finding_Non_Existing_Item_Returns_Null()
        {
            var AdRepository = GetAdRepository();

            var returnedAd = AdRepository.FindBy(ad => ad.ID == 0);
            Assert.IsNull(returnedAd);
        }

        [Test]
        public void Finding_Non_Existing_List_Returns_Empty_List()
        {
            var returnedAds = GetAdFinder().FindBy(ad => ad.Price == 0);
            Assert.NotNull(returnedAds);
            Assert.AreEqual(0, returnedAds.ToList().Count());
        }

        [Test]
        public void Removing_Null_Item_Throws_Exception()
        {
            var adRepository = GetAdRepository();
            Assert.Throws<ArgumentNullException>(() => adRepository.Remove((Ad)null));
        }

        [Test]
        public void Removing_Valid_Ad()
        {
            var customerRepository = GetCustomerRepository();
            var customer = CreateNewCustomer();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.NotNull(returnedCustomer);

            var AdRepository = GetAdRepository();

            var category = CreateAndGetCategory("Video Games");
            var item = CreateAndGetItem("XBox 360", "XBox 360 Black", category);

            var Ad = CreateNewAd(customer, item, "XBox 360 Black Description", 1800, "Good",
                                                   @"Resource\Xbox360.jpg");

            AdRepository.Add(Ad);
            AdRepository.UnitOfWork.Commit();

            var returnedAd = AdRepository.FindBy(ad => ad.Description == Ad.Description);
            Assert.NotNull(returnedAd);

            AdRepository.Remove(returnedAd);
            AdRepository.UnitOfWork.Commit();
            returnedAd = AdRepository.FindBy(ad => ad.Description == Ad.Description);
            Assert.Null(returnedAd);
        }

        [Test]
        public void Removing_Valid_Customer()
        {
            var customerRepository = GetCustomerRepository();
            var customer = CreateNewCustomer();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.NotNull(returnedCustomer);

            customerRepository.Remove(customerRepository.Find(returnedCustomer.ID));
            customerRepository.UnitOfWork.Commit();

            returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.Null(returnedCustomer);
        }

        [Test]
        public void Updating_Valid_Customer()
        {
            var customerRepository = GetCustomerRepository();
            var customer = CreateNewCustomer();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.NotNull(returnedCustomer);

            returnedCustomer.FirstName = "Updated Name";

           // customerRepository.Update(returnedCustomer);
            customerRepository.UnitOfWork.Commit();

            returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.AreEqual("Updated Name", returnedCustomer.FirstName);
        }

        [Test]
        public void Removing_Customer_With_Ads_Throws_Exception()
        {
            var customerRepository = GetCustomerRepository();
            var customer = CreateNewCustomer();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var returnedCustomer = GetCustomerFinder().FindByEmail(customer.Email);
            Assert.NotNull(returnedCustomer);

            var adRepository = GetAdRepository();
            var category = CreateAndGetCategory("Video Games");
            var item = CreateAndGetItem("XBox 360", "XBox 360 Black", category);

            var ad = CreateNewAd(customer, item, "XBox 360 Black Description", 1800, "Good",
                                                   @"Resource\Xbox360.jpg");

            adRepository.Add(ad);
            adRepository.UnitOfWork.Commit();

            var returnedAd = adRepository.FindBy(a => a.Description == ad.Description);
            Assert.NotNull(returnedAd);

            var customerToRemove = customerRepository.Find(returnedCustomer.ID);
            customerRepository.Remove(customerToRemove);

            Assert.Throws<GenericADOException>(() => customerRepository.UnitOfWork.Commit());
        }

        [Test]
        public void Getting_Page_Of_Multiple_Ads()
        {
            var customer = CreateNewCustomer();

            var customerRepository = GetCustomerRepository();

            customerRepository.Add(customer);
            customerRepository.UnitOfWork.Commit();

            var AdRepository = GetAdRepository();

            var category = CreateAndGetCategory("Video Games");
            var item = CreateAndGetItem("XBox 360", "XBox 360 Black", category);

            var Ad = CreateNewAd(customer, item, "XBox 360 Black Description", 1800, "Good",
                                                   @"Resource\Xbox360.jpg");

            AdRepository.Add(Ad);

            Ad = CreateNewAd(customer, item, "Xbox 360 Halo 3rd Edition Console", 2400, "Very Good",
                                                   @"Resource\xbox360Halo3EditionConsole.jpg");
            AdRepository.Add(Ad);

            Ad = CreateNewAd(customer, item, "Xbox 360 Wireless Controller", 450, "As New",
                                                   @"Resource\Xbox360WirelessController.jpg");
            AdRepository.Add(Ad);

            AdRepository.UnitOfWork.Commit();

            var returnedAds = GetAdFinder().FindAll(1, 2);

            Assert.NotNull(returnedAds);
            var adsList = returnedAds.ToList();
            Assert.AreEqual(1, adsList.Count);
            Assert.AreEqual("Xbox 360 Wireless Controller", adsList[0].Description);

            returnedAds = GetAdFinder().FindAll(0, 2);
            Assert.NotNull(returnedAds);
            adsList = returnedAds.ToList();
            Assert.AreEqual(2, adsList.Count);
            Assert.AreEqual("Xbox 360 Halo 3rd Edition Console", adsList[1].Description);
        }
    }
}
